/*
* Copyright (c) 2012 Kyle Hayward
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

#pragma once

using System::Runtime::InteropServices::OutAttribute;

namespace OptixDotNet {
namespace Math
{
	value class Matrix4x4;

	public value class UByte2
	{
	public:
		UByte2( unsigned char x, unsigned char y ) : X(x), Y(y) { }

		static property int SizeInBytes { int get() { return System::Runtime::InteropServices::Marshal::SizeOf(UByte2::typeid); } }

		static explicit operator UByte2^ ( unsigned int val )
		{
			return gcnew UByte2( ( val >> 8 ) & 255, val & 255 );
		}

		static explicit operator unsigned int ( UByte2^ val )
		{
			return ( val->X << 8 ) | val->Y;
		}

		virtual String^ ToString() override
		{
			return ( X.ToString() + ", " + Y );
		}
	public:
		unsigned char X, Y;
	};

	public value class UByte3
	{
	public:
		UByte3( unsigned char x, unsigned char y, unsigned char z ) : X(x), Y(y), Z(z) { }

		static property int SizeInBytes { int get() { return System::Runtime::InteropServices::Marshal::SizeOf(UByte3::typeid); } }

		static explicit operator UByte3^ ( unsigned int val )
		{
			return gcnew UByte3( ( val >> 16 ) & 255, ( val >> 8 ) & 255, val & 255 );
		}

		static explicit operator unsigned int ( UByte3^ val )
		{
			return ( val->X << 16 ) | ( val->Y << 8 ) | val->Z;
		}

		virtual String^ ToString() override
		{
			return ( X.ToString() + ", " + Y + ", " + Z );
		}
	public:
		unsigned char X, Y, Z;
	};

	public value class UByte4
	{
	public:
		UByte4( unsigned char x, unsigned char y, unsigned char z, unsigned char w ) : X(x), Y(y), Z(z), W(w) { }

		static property int SizeInBytes { int get() { return System::Runtime::InteropServices::Marshal::SizeOf(UByte4::typeid); } }

		static explicit operator UByte4^ ( unsigned int val )
		{
			return gcnew UByte4( ( val >> 24 ) & 255, ( val >> 16 ) & 255, ( val >> 8 ) & 255, val & 255 );
		}

		static explicit operator unsigned int ( UByte4^ val )
		{
			return ( val->X << 24 ) | ( val->Y << 16 ) | ( val->Z << 8 ) | val->W;
		}

		virtual String^ ToString() override
		{
			return ( X.ToString() + ", " + Y + ", " + Z + ", " + W );
		}
	public:
		unsigned char X, Y, Z, W;
	};

	public value class Int2
	{
	public:
		Int2( int x, int y ) : X(x), Y(y) { }

		static property int SizeInBytes { int get() { return System::Runtime::InteropServices::Marshal::SizeOf(Int2::typeid); } }

		virtual String^ ToString() override
		{
			return ( X.ToString() + ", " + Y );
		}
	public:
		int X, Y;
	};

	public value class Int3
	{
	public:
		Int3( int x, int y, int z ) : X(x), Y(y), Z(z) { }

		static property int SizeInBytes { int get() { return System::Runtime::InteropServices::Marshal::SizeOf(Int3::typeid); } }

		virtual String^ ToString() override
		{
			return ( X.ToString() + ", " + Y + ", " + Z );
		}
	public:
		int X, Y, Z;
	};

	public value class Int4
	{
	public:
		Int4( int x, int y, int z, int w ) : X(x), Y(y), Z(z), W(w) { }

		static property int SizeInBytes { int get() { return System::Runtime::InteropServices::Marshal::SizeOf(Int4::typeid); } }

		virtual String^ ToString() override
		{
			return ( X.ToString() + ", " + Y + ", " + Z + ", " + W );
		}
	public:
		int X, Y, Z, W;
	};

	public value class UInt2
	{
	public:
		UInt2( unsigned int x, unsigned int y ) : X(x), Y(y) { }

		static property int SizeInBytes { int get() { return System::Runtime::InteropServices::Marshal::SizeOf(UInt2::typeid); } }

		virtual String^ ToString() override
		{
			return ( X.ToString() + ", " + Y );
		}
	public:
		unsigned int X, Y;
	};

	public value class UInt3
	{
	public:
		UInt3( unsigned int x, unsigned int y, unsigned int z ) : X(x), Y(y), Z(z) { }

		static property int SizeInBytes { int get() { return System::Runtime::InteropServices::Marshal::SizeOf(UInt3::typeid); } }

		virtual String^ ToString() override
		{
			return ( X.ToString() + ", " + Y + ", " + Z );
		}
	public:
		unsigned int X, Y, Z;
	};

	public value class UInt4
	{
	public:
		UInt4( unsigned int x, unsigned int y, unsigned int z, unsigned int w ) : X(x), Y(y), Z(z), W(w) { }

		static property int SizeInBytes { int get() { return System::Runtime::InteropServices::Marshal::SizeOf(UInt4::typeid); } }

		virtual String^ ToString() override
		{
			return ( X.ToString() + ", " + Y + ", " + Z + ", " + W );
		}
	public:
		unsigned int X, Y, Z, W;
	};

	public value class Vector2
	{
	public:
		Vector2( float x, float y ) : X(x), Y(y) { }

		static property int SizeInBytes { int get() { return System::Runtime::InteropServices::Marshal::SizeOf(Vector2::typeid); } }

		inline static Vector2 operator * ( Vector2 l, float x )
		{
			Vector2 res = l;
			res.X *= x;
			res.Y *= x;

			return res;
		}

		inline static Vector2 operator * ( Vector2 l, Vector2 r )
		{
			Vector2 res = l;
			res.X *= r.X;
			res.Y *= r.Y;

			return res;
		}

		inline static Vector2 operator / ( Vector2 l, float x )
		{
			Vector2 res = l;
			res.X /= x;
			res.Y /= x;

			return res;
		}
		
		inline static Vector2 operator / ( Vector2 l, Vector2 r )
		{
			Vector2 res = l;
			res.X /= r.X;
			res.Y /= r.Y;

			return res;
		}

		inline static Vector2 operator + ( Vector2 l, float x )
		{
			Vector2 res = l;
			res.X += x;
			res.Y += x;

			return res;
		}

		inline static Vector2 operator + ( Vector2 l, Vector2 r )
		{
			Vector2 res = l;
			res.X += r.X;
			res.Y += r.Y;

			return res;
		}

		inline static Vector2 operator - ( Vector2 l, float x )
		{
			Vector2 res = l;
			res.X -= x;
			res.Y -= x;

			return res;
		}

		inline static Vector2 operator - ( Vector2 l, Vector2 r )
		{
			Vector2 res = l;
			res.X -= r.X;
			res.Y -= r.Y;

			return res;
		}

		inline static float Dot( Vector2% a, Vector2% b )
		{
			return a.X * b.X + a.Y * b.Y;
		}

		inline float Dot( Vector2 b )
		{
			return X * b.X + Y * b.Y;
		}

		inline float Length()
		{
			return sqrtf( X * X + Y * Y );
		}

		inline void Normalize()
		{
			float inv = 1.0f / Length();
			(*this) *= inv;
		}

		
		virtual String^ ToString() override
		{
			return ( X.ToString() + ", " + Y );
		}

	public:
		float X, Y;
	};

	public value class Vector3
	{
	public:
		Vector3( float x, float y, float z ) : X(x), Y(y), Z(z) { }

		inline void Set( float x, float y, float z )
		{
			X = x;
			Y = y;
			Z = z;
		}

		inline void Set( Vector3 vec )
		{
			X = vec.X;
			Y = vec.Y;
			Z = vec.Z;
		}

		inline float Length()
		{
			return (float)System::Math::Sqrt( X * X + Y * Y + Z * Z );
		}

		inline float LengthSqrd()
		{
			return ( X * X + Y * Y + Z * Z );
		}

		inline void Normalize()
		{
			float length = Length();
			if( length == 0 )
				return;

			float inv = 1.0f / length;
			X *= inv;
			Y *= inv;
			Z *= inv;
		}

		/// <summary>
		/// Gets or Sets the component at index. 
        /// </summary>
		/// <exception cref="ArgumentOutOfRangeException">Index outside the number of vector components.</exception>
		property float default[int]
		{
			inline float get ( int index )
			{
				switch( index )
				{
				case 0:
					return X;
				case 1:
					return Y;
				case 2:
					return Z;
				default:
					throw gcnew System::ArgumentOutOfRangeException( "index", "index outside the number of vector components" );
				}
			}
			inline void set( int index, float x )
			{
				switch( index )
				{
				case 0:
					X = x;
					break;
				case 1:
					Y = x;
					break;
				case 2:
					Z = x;
					break;
				default:
					throw gcnew System::ArgumentOutOfRangeException( "index", "Index outside the number of vector components" );
				}
			}
		}

		static property Vector3 One { Vector3 get() { return Vector3(1, 1, 1); } }

		static property Vector3 Zero { Vector3 get() { return Vector3(0, 0, 0); } }

		static property Vector3 UnitX { Vector3 get() { return Vector3(1, 0, 0); } }

		static property Vector3 UnitY { Vector3 get() { return Vector3(0, 1, 0); } }

		static property Vector3 UnitZ { Vector3 get() { return Vector3(0, 0, 1); } }

		static property int SizeInBytes { int get() { return System::Runtime::InteropServices::Marshal::SizeOf(Vector3::typeid); } }

		inline static Vector3 operator * ( Vector3 l, float x )
		{
			Vector3 res = l;
			res.X *= x;
			res.Y *= x;
			res.Z *= x;

			return res;
		}

		inline static Vector3 operator * ( float x, Vector3 l )
		{
			Vector3 res = l;
			res.X *= x;
			res.Y *= x;
			res.Z *= x;

			return res;
		}

		inline static Vector3 operator * ( Vector3 l, Vector3 r )
		{
			Vector3 res = l;
			res.X *= r.X;
			res.Y *= r.Y;
			res.Z *= r.Z;

			return res;
		}

		inline static Vector3 operator / ( Vector3 l, float x )
		{
			Vector3 res = l;
			res.X /= x;
			res.Y /= x;
			res.Z /= x;

			return res;
		}
		
		inline static Vector3 operator / ( Vector3 l, Vector3 r )
		{
			Vector3 res = l;
			res.X /= r.X;
			res.Y /= r.Y;
			res.Z /= r.Z;

			return res;
		}

		inline static Vector3 operator + ( Vector3 l, float x )
		{
			Vector3 res = l;
			res.X += x;
			res.Y += x;
			res.Z += x;

			return res;
		}

		inline static Vector3 operator + ( Vector3 l, Vector3 r )
		{
			Vector3 res = l;
			res.X += r.X;
			res.Y += r.Y;
			res.Z += r.Z;

			return res;
		}

		inline static Vector3 operator - ( Vector3 l, float x )
		{
			Vector3 res = l;
			res.X -= x;
			res.Y -= x;
			res.Z -= x;

			return res;
		}

		inline static Vector3 operator - ( Vector3 l, Vector3 r )
		{
			Vector3 res = l;
			res.X -= r.X;
			res.Y -= r.Y;
			res.Z -= r.Z;

			return res;
		}

		inline static void Cross( Vector3% a, Vector3% b, [Out] Vector3% result )
		{
			result.X = a.Y * b.Z - a.Z * b.Y;
			result.Y = a.Z * b.X - a.X * b.Z;
			result.Z = a.X * b.Y - a.Y * b.X;
		}

		inline static Vector3 Cross( Vector3% a, Vector3% b )
		{
			Vector3 res;
			res.X = a.Y * b.Z - a.Z * b.Y;
			res.Y = a.Z * b.X - a.X * b.Z;
			res.Z = a.X * b.Y - a.Y * b.X;

			return res;
		}

		inline Vector3 Cross( Vector3 b )
		{
			Vector3 res;
			res.X = Y * b.Z - Z * b.Y;
			res.Y = Z * b.X - X * b.Z;
			res.Z = X * b.Y - Y * b.X;

			return res;
		}

		inline static float Dot( Vector3% a, Vector3% b )
		{
			return a.X * b.X + a.Y * b.Y + a.Z * b.Z;
		}

		inline float Dot( Vector3 b )
		{
			return X * b.X + Y * b.Y + Z * b.Z;
		}

		inline static void Lerp( Vector3% a, Vector3% b, float t, [Out] Vector3% result )
		{
			result.X = a.X + ( b.X - a.X ) * t;
			result.Y = a.Y + ( b.Y - a.Y ) * t;
			result.Z = a.Z + ( b.Z - a.Z ) * t;
		}

		inline static Vector3 Lerp( Vector3 a, Vector3 b, float t )
		{
			Vector3 res;
			res.X = a.X + ( b.X - a.X ) * t;
			res.Y = a.Y + ( b.Y - a.Y ) * t;
			res.Z = a.Z + ( b.Z - a.Z ) * t;

			return res;
		}

		inline static void Clamp( Vector3% value, Vector3% min, Vector3% max, [Out] Vector3% result )
		{
			float x = value.X;
			x = (x > max.X) ? max.X : x;
			x = (x < min.X) ? min.X : x;

			float y = value.Y;
			y = (y > max.Y) ? max.Y : y;
			y = (y < min.Y) ? min.Y : y;

			float z = value.Z;
			z = (z > max.Z) ? max.Z : z;
			z = (z < min.Z) ? min.Z : z;

			result.X = x;
			result.Y = y;
			result.Z = z;
		}

		inline static Vector3 Clamp( Vector3 value, Vector3 min, Vector3 max )
		{
			float x = value.X;
			x = (x > max.X) ? max.X : x;
			x = (x < min.X) ? min.X : x;

			float y = value.Y;
			y = (y > max.Y) ? max.Y : y;
			y = (y < min.Y) ? min.Y : y;

			float z = value.Z;
			z = (z > max.Z) ? max.Z : z;
			z = (z < min.Z) ? min.Z : z;

			return Vector3( x, y, z );
		}
		
		/// <summary>
		/// Transforms a vector by a matrix with w = 1 (i.e. using the translation row of the Matrix).
		/// </summary>
		static void Transform( Vector3% vec, Matrix4x4% matrix, [Out] Vector3% result );

		/// <summary>
		/// Transforms a vector by a matrix with w = 1 (i.e. using the translation row of the Matrix).
		/// </summary>
		static Vector3 Transform( Vector3% vec, Matrix4x4% matrix );

		/// <summary>
		/// Transforms an array of vectors by a matrix with w = 1 (i.e. using the translation row of the Matrix).
		/// </summary>
		static void Transform( array<Vector3>^ vectorArray, Matrix4x4% matrix );

		/// <summary>
		/// Transforms a vector by a matrix with w = 1 (i.e. using the translation row of the Matrix).
		/// </summary>
		void Transform( Matrix4x4 matrix );

		/// <summary>
		/// Transforms a vector by a matrix with w = 0 (i.e. without the translation row of the Matrix).
		/// </summary>
		static void TransformNormal( Vector3% vec, Matrix4x4% matrix, [Out] Vector3% result );

		/// <summary>
		/// Transforms a vector by a matrix with w = 0 (i.e. without the translation row of the Matrix).
		/// </summary>
		static Vector3 TransformNormal( Vector3% vec, Matrix4x4% matrix );

		/// <summary>
		/// Transforms an array of vectors by a matrix with w = 0 (i.e. without the translation row of the Matrix).
		/// </summary>
		static void TransformNormal( array<Vector3>^ vectorArray, Matrix4x4% matrix );

		/// <summary>
		/// Transforms a vector by a matrix with w = 0 (i.e. without the translation row of the Matrix).
		/// </summary>
		void TransformNormal( Matrix4x4 matrix );

		/// <summary>
		/// Transforms a vector by a matrix and projects the result back into w = 1.
		/// </summary>
		static void TransformCoord( Vector3% vec, Matrix4x4% matrix, [Out] Vector3% result );

		/// <summary>
		/// Transforms a vector by a matrix and projects the result back into w = 1.
		/// </summary>
		static Vector3 TransformCoord( Vector3% vec, Matrix4x4% matrix );

		/// <summary>
		/// Transforms an array of vectors by a matrix and projects the result back into w = 1.
		/// </summary>
		static void TransformCoord( array<Vector3>^ vectorArray, Matrix4x4% matrix );

		/// <summary>
		/// Transforms a vector by a matrix and projects the result back into w = 1.
		/// </summary>
		void TransformCoord( Matrix4x4 matrix );

		virtual String^ ToString() override
		{
			return ( X.ToString() + ", " + Y + ", " + Z );
		}
	public:
		float X, Y, Z;
	};

	public value class Vector4
	{
	public:
		Vector4( float x, float y, float z, float w ) : X(x), Y(y), Z(z), W(w) { }
		Vector4( Vector3 v, float w ) : X(v.X), Y(v.Y), Z(v.Z), W(w) { }

		inline void Set( float x, float y, float z, float w )
		{
			X = x;
			Y = y;
			Z = z;
			W = w;
		}

		inline void Set( float x, float y, float z )
		{
			X = x;
			Y = y;
			Z = z;
		}

		inline void Set( Vector4 vec )
		{
			X = vec.X;
			Y = vec.Y;
			Z = vec.Z;
			W = vec.W;
		}

		inline void Set( Vector3 vec )
		{
			X = vec.X;
			Y = vec.Y;
			Z = vec.Z;
		}

		static property Vector4 One { Vector4 get() { return Vector4(1, 1, 1, 1); } }
		static property Vector4 Zero { Vector4 get() { return Vector4(0, 0, 0, 0); } }

		static property Vector4 UnitX { Vector4 get() { return Vector4(1, 0, 0, 0); } }

		static property Vector4 UnitY { Vector4 get() { return Vector4(0, 1, 0, 0); } }

		static property Vector4 UnitZ { Vector4 get() { return Vector4(0, 0, 1, 0); } }

		static property Vector4 UnitW { Vector4 get() { return Vector4(0, 0, 0, 1); } }

		static property int SizeInBytes { int get() { return System::Runtime::InteropServices::Marshal::SizeOf(Vector4::typeid); } }

		static Vector4 CreatePlane( Vector3 normal, Vector3 point )
		{
			normal.Normalize();
			float d = -normal.Dot( point );

			return Vector4( normal, d );
		}
		
		inline static float Dot( Vector4% a, Vector4% b )
		{
			return a.X * b.X + a.Y * b.Y + a.Z * b.Z + a.W * b.W;
		}

		inline float Dot( Vector4 b )
		{
			return X * b.X + Y * b.Y + Z * b.Z + W * b.W;
		}

		inline static Vector4 operator * ( Vector4 l, float x )
		{
			Vector4 res = l;
			res.X *= x;
			res.Y *= x;
			res.Z *= x;
			res.W *= x;

			return res;
		}

		inline static Vector4 operator * ( Vector4 l, Vector4 r )
		{
			Vector4 res = l;
			res.X *= r.X;
			res.Y *= r.Y;
			res.Z *= r.Z;
			res.W *= r.W;

			return res;
		}

		virtual String^ ToString() override
		{
			return ( X.ToString() + ", " + Y + ", " + Z + ", " + W );
		}

	public:
		float X, Y, Z, W;
	};
}
}